#pragma once
#include <iostream>
#include <memory>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Comm.hpp"
#include "Connection.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"

using namespace Calculator;

static const int buffersize = 1024; 

void Reader(Connection *conn);
void Sender(Connection *conn);
void Exceptor(Connection *conn);


void RequestHandler(Connection* conn)
{
    // std::cout << "RequestHandler" << std::endl; // Debug
    std::unique_ptr<Protocol> protocol = make_unique<Protocol>();
    std::string msg;
    // 解析字节流，得到一个完整的独立的报文
    while(protocol->Unpacking(conn->_inbuffer, &msg))
    {
        // 将完整独立报文进行反序列化，形成有效数据
        std::unique_ptr<Request> req = make_unique<Request>();
        bool ok = true;;
        req->Deserialize(msg, &ok);
        // 对数据进行处理
        auto resp = req->Result();
        // 对处理好的结果，进行序列化
        std::string content;
        resp->Serialize(&content);

        // 将序列化好的结果进行封装
        protocol->Package(content, &content);

        // 发出数据
        conn->_outbuffer += content;
    }

    // 从这里出来之后，意味着可能完成循环中的过程，又或者没有，所以这里我们通过判断conn中的outbuffer是否为空，来决定是否将数据发出
    if(!conn->OutEmpty())
    {
        Sender(conn);
    }
}

void Accept(Connection *conn)
{
    errno = 0;
    // std::cout << "new link" << std::endl;
    while (true)
    {
        struct sockaddr_in peeraddr;
        socklen_t len = sizeof(peeraddr);
        int newsock = accept(conn->GetSockFd(), (struct sockaddr *)(&peeraddr), &len);

        if (newsock < 0)
        {
            if (errno == EAGAIN)
                break;
            else if (errno == EINTR)
                continue;
            else
            {
                LOG(Error, "failed to obtain connection\n");
                break; // 读取错误， 结束循环
            }
        }

        // 获取新连接之后，将该文件描述符设为非阻塞
        SetNonBlock(newsock);

        // 获取新连接之后，建立普通连接对象
        Connection *newconn = Factory::BuildNormalConn(newsock, EPOLLIN | EPOLLET, conn->_R, Reader, Sender, Exceptor);
        // 建立好连接对象之后，将该连接对象添加到Reactor中
        conn->_R->AddConnection(newconn);
    }
}

void Reader(Connection *conn)
{
    // std::cout << "Reader..." << std::endl;
    errno = 0;
    while(true)
    {
        char buffer[buffersize];
        int n = recv(conn->GetSockFd(), buffer, sizeof(buffer) - 1, 0);


        // std::cout << "111111111111: " << n <<std::endl; // Debug

        if(n > 0)
        {
            buffer[n] = 0;
            conn->_inbuffer += buffer;

            // Debug
            // std::cout << conn->_inbuffer << std::endl;
        }
        else if(n == 0)
        {
            LOG(Info, "close the connection on the other end\n");
            Exceptor(conn);
            return;
        }
        else
        {
            if(errno == EAGAIN) break; // 该循环中，只会从这个地方跳出
            else if(errno == EINTR) continue;
            else
            {
                LOG(Error, "reading data error\n");
                Exceptor(conn);
                return;
            }
        }
    }

    // 跳出循环意味着本轮读取完毕，同时也意味着我们该处理输入缓冲区中的内容了
    RequestHandler(conn);
}

void Sender(Connection *conn)
{
    // std::cout << "Sender..." << std::endl;
    errno = 0;
    while(true)
    {
        int n = send(conn->GetSockFd(), conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);

        if(n >= 0)
        {
            conn->_outbuffer.erase(0, n);
            if(conn->OutEmpty()) break;
        }
        else
        {
            if(errno == EAGAIN) break;
            else if(errno == EINTR) continue;
            else
            {
                LOG(Error, "sending data error\n");
                Exceptor(conn);
                return;
            }
        }
    }

    // 从循环中出来之后，只有两种情况，一种是发送条件不具备不让继续发送了
    // 一种是需要发送的数据发完了（但是我们遵循写事件按需关心，可能该文件描述符上仍然关心写事件，所以我们应该更改该文件描述述符对读写事件的关心状态）
    if(conn->OutEmpty())
    {
        conn->_R->ModConnection(conn, true, true);
    }
    else
    {
        conn->_R->ModConnection(conn, true, false);
    }
}

void Exceptor(Connection *conn)
{
    // 进入当一个文件描述符进入异常之后，我们能做的操作只有关闭该连接，而关闭连接，则意味着要做以下事情

    LOG(Info, "the %d connection has been closed, possibly due to the other end being closed or an error may have occurred\n", conn->GetSockFd());
    // 从connections中将该连接对象去除
    // 从epoll模型中移除该文件描述符
    conn->_R->DelConnection(conn);

    // 关闭连接
    conn->Close();
    // 释放该连接对象
    delete conn;
}